/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.ui.models;

import bgu.sonar.util.collections.obs.ObservableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.ComboBoxModel;
import javax.swing.Icon;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import org.jfree.data.DomainOrder;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetChangeListener;
import org.jfree.data.general.DatasetGroup;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYDataset;

/**
 *
 * @author bennyl
 */
public abstract class AbstractTabularModel<T> implements TableModel, ObservableList.ListListener<T>, XYDataset, IntervalXYDataset, ComboBoxModel<T>, ListModel<T> {

    private static XYSeriesInfo DEFAULT_SERIES_INFO = new XYSeriesInfo("", 0, 1);
    private ObservableList<T> elements;
    private LinkedList modelListeners;
    private String[] columns;
    private Class[] editableColumns;
    private Icon[] columnsIcons;
    private String name = "";
    private double intervalWidth = 1;
    private List<XYSeriesInfo> seriesInfo = new ArrayList<>();
    private Object selectedItem;

    @SuppressWarnings("LeakingThisInConstructor")
    public AbstractTabularModel(String... columns) {
        this.modelListeners = new LinkedList<>();
        this.elements = new ObservableList<>(new ArrayList<T>());
        setColumns(columns);
        this.elements.getListeners().add(this);
        this.seriesInfo.add(DEFAULT_SERIES_INFO);
    }

    public void setColumnEditable(int columnNumber, Class type) {
        editableColumns[columnNumber] = type;
        fireChanged(true);
    }

    public int getColumnIndex(String name) {
        for (int i = 0; i < columns.length; i++) {
            if (columns[i].equals(name)) {
                return i;
            }
        }

        return -1;
    }

    public void setColumnIcon(int columnIndex, Icon icon) {
        columnsIcons[columnIndex] = icon;
    }

    public void addSeriesInfo(XYSeriesInfo info) {
        seriesInfo.remove(DEFAULT_SERIES_INFO);
        seriesInfo.add(info);
    }

    public void setColumns(String... columns) {
        this.columns = columns;
        this.editableColumns = new Class[columns.length];
        this.columnsIcons = new Icon[columns.length];
        fireChanged(true);
    }

    public ObservableList<T> getElements() {
        return elements;
    }

    public Icon getColumnIcon(int columnIdx) {
        return columnsIcons[columnIdx];
    }

    public String getName() {
        return name;
    }

    public void fireChanged(boolean stractural) {
        for (Object l : modelListeners) {
            if (l instanceof TableModelListener) {
                if (stractural) {
                    ((TableModelListener) l).tableChanged(new TableModelEvent(this, TableModelEvent.HEADER_ROW));
                } else {
                    ((TableModelListener) l).tableChanged(new TableModelEvent(this));
                }
            } else if (l instanceof DatasetChangeListener) {
                ((DatasetChangeListener) l).datasetChanged(new DatasetChangeEvent(this, this));
            } else if (l instanceof ListDataListener) {
                ((ListDataListener) l).contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize() - 1));
            }
        }
    }

    public void sortByColumn(final int column, final Comparator<Object> comparator) {
        elements.getListeners().remove(this);
        Collections.sort(elements, new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
                return comparator.compare(extractValue(o1, columns[column]), extractValue(o2, columns[column]));
            }
        });
        elements.getListeners().add(this);
        fireChanged(false);
    }

    /*'****************************
     * TABLE MODEL IMPLEMENTATION *
     ******************************/
    @Override
    public int getRowCount() {
        return elements.size();
    }

    @Override
    public int getColumnCount() {
        return columns.length;
    }

    @Override
    public String getColumnName(int columnIndex) {
        return columns[columnIndex];
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return editableColumns[columnIndex] == null ? String.class : editableColumns[columnIndex];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return editableColumns[columnIndex] != null;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (columns.length > 0) {
            return extractValue(elements.get(rowIndex), columns[columnIndex]);
        }

        return 0;
    }

    public abstract Object extractValue(T element, String value);

    public void importValue(T element, String column, Object value) {
        throw new UnsupportedOperationException("Not Implemented!"); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        importValue(elements.get(rowIndex), columns[columnIndex], aValue);
    }

    @Override
    public void addTableModelListener(TableModelListener l) {
        this.modelListeners.add(l);
    }

    @Override
    public void removeTableModelListener(TableModelListener l) {
        this.modelListeners.remove(l);
    }

    /*'***************************
     * XY DATASET IMPLEMENTATION * 
     *****************************/
    @Override
    public DomainOrder getDomainOrder() {
        return DomainOrder.NONE;
    }

    @Override
    public int getItemCount(int series) {
        return getRowCount(); //there is only one series
    }

    @Override
    public Number getX(int series, int item) {
        return getXValue(series, item);
    }

    @Override
    public double getXValue(int series, int item) {
        return Double.valueOf("" + getValueAt(item, seriesInfo.get(series).xColumn));
    }

    @Override
    public Number getY(int series, int item) {
        return getYValue(series, item);
    }

    @Override
    public double getYValue(int series, int item) {
        return Double.valueOf("" + getValueAt(item, seriesInfo.get(series).yColumn));
    }

    @Override
    public int getSeriesCount() {
        return seriesInfo.size();
    }

    @Override
    public Comparable getSeriesKey(int series) {
        return seriesInfo.get(series).getSeriesName();
    }

    @Override
    public int indexOf(Comparable seriesKey) {
        String seriesName = seriesKey.toString();

        for (int i = 0; i < seriesInfo.size(); i++) {
            if (seriesInfo.get(i).getSeriesName().equals(seriesName)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void addChangeListener(DatasetChangeListener listener) {
        modelListeners.add(listener);
    }

    @Override
    public void removeChangeListener(DatasetChangeListener listener) {
        modelListeners.remove(listener);
    }

    @Override
    public DatasetGroup getGroup() {
        return null;
    }

    @Override
    public void setGroup(DatasetGroup group) {
    }

    /**
     * ************************************
     * INTERVAL XY DATASET IMPLEMENTATION * ************************************
     */
    @Override
    public Number getStartX(int series, int item) {
        return getX(series, item);
    }

    @Override
    public double getStartXValue(int series, int item) {
        return getXValue(series, item);
    }

    @Override
    public Number getEndX(int series, int item) {
        return getX(series, item).doubleValue() + intervalWidth;
    }

    @Override
    public double getEndXValue(int series, int item) {
        return getXValue(series, item) + intervalWidth;
    }

    @Override
    public Number getStartY(int series, int item) {
        return 0;
    }

    @Override
    public double getStartYValue(int series, int item) {
        return 0;
    }

    @Override
    public Number getEndY(int series, int item) {
        return getY(series, item);
    }

    @Override
    public double getEndYValue(int series, int item) {
        return getYValue(series, item);
    }

    public void clearSeriesInfo() {
        seriesInfo.clear();
    }

    @Override
    public void onItemsAdded(ObservableList<T> list, Collection<T> items, int pos) {
        fireChanged(false);
    }

    @Override
    public void onItemsRemoved(ObservableList<T> list, Collection<T> items) {
        fireChanged(false);
    }

    @Override
    public void onListChanged(ObservableList<T> list) {
        fireChanged(true);
    }

    @Override
    public void setSelectedItem(Object anItem) {
        selectedItem = anItem;
    }

    @Override
    public Object getSelectedItem() {
        return selectedItem;
    }

    @Override
    public int getSize() {
        return getElements().size();
    }

    @Override
    public T getElementAt(int index) {
        return getElements().get(index);
    }

    @Override
    public void addListDataListener(ListDataListener l) {
        modelListeners.add(l);
    }

    @Override
    public void removeListDataListener(ListDataListener l) {
        modelListeners.remove(l);
    }
}
